package com.liruan.rest.aop;

import java.time.LocalDateTime;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.liruan.rest.common.SSErrorCode;
import com.liruan.rest.dao.UserInfoMapper;
import com.liruan.rest.dto.AuthDTO;
import com.liruan.rest.po.UserAccountPO;
import com.liruan.rest.service.UserService;
import com.msxf.ai.rest.dto.ErrorCodeOnlyDTO;
import com.msxf.ai.rest.response.JSONResponse;

/**
 * 用以实现权限系统的AOP拦截器
 *
 */
@Aspect
@Service
public class AuthInterceptor implements UserService, InitializingBean
{
	private static final Logger LOGGER = LoggerFactory.getLogger(AuthInterceptor.class);

	private static final String SESSION_HEADER = "SessionId";

	private static final Long SESSION_EXPIRE_DURATION = 3600L;

	private static final long SESSION_CHECKER_DURATION = 1000L * 10;

	private ThreadLocal<String> currentSession = ThreadLocal.withInitial(() -> {
		return null;
	});

	@Autowired
	private UserInfoMapper userInfoMapper;

	private ConcurrentMap<String, SessionInfo> sessionMap = new ConcurrentHashMap<>();

	@Pointcut("execution(* com.liruan.rest.controller..*(..)) && @annotation(com.liruan.rest.annotation.AuthRequired)")
	public void controllerMethodPointcut()
	{
	}

	@Around("controllerMethodPointcut()")
	public Object auth(ProceedingJoinPoint pjp) throws Throwable
	{
		String methodName = pjp.getStaticPart().getSignature().getName();
		try
		{
			HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
					.getRequest();
			String sessionId = request.getHeader(SESSION_HEADER);
			LOGGER.info("请求的方法名:[{}] sessionId:[{}]", methodName, sessionId);
			if (Objects.isNull(sessionId))
			{
				LOGGER.info("请求的方法名:[{}] 没有会话信息", methodName);
				return new JSONResponse<>(SSErrorCode.NO_SUCH_SESSION);
			}

			synchronized (sessionId.intern())
			{
				SessionInfo sessionInfo = sessionMap.get(sessionId);
				if (Objects.isNull(sessionInfo))
				{
					LOGGER.info("请求的方法名:[{}] sessionId:[{}] 会话不存在", methodName, sessionId);
					return new JSONResponse<>(SSErrorCode.NO_SUCH_SESSION);
				}

				sessionInfo.lastUpdateTime = LocalDateTime.now();
				currentSession.set(sessionId);

			}
		}
		catch (Exception e)
		{
			LOGGER.info("调用接口:[{}]  认证失败!", methodName, e);
			return new JSONResponse<>(SSErrorCode.API_AUTH_FAIL);
		}
		finally
		{
			currentSession.set(null);
		}

		try
		{
			return pjp.proceed();
		}
		finally
		{
			currentSession.set(null);
		}

	}

	@Override
	public AuthDTO login(String account, String password)
	{
		try
		{
			UserAccountPO po = userInfoMapper.findUser(account, password);
			if (Objects.isNull(po) || Objects.isNull(po.getId()))
			{
				LOGGER.info("用户:[{}] 密码:[{}] 不匹配", account, password);
				return new AuthDTO(SSErrorCode.ACCOUNT_OR_PASSWORD_NOT_MATCH);
			}

			LOGGER.info("用户:[{}] 密码:[{}] 匹配", account, password);
			String sessionId = UUID.randomUUID().toString();
			SessionInfo sessionInfo = new SessionInfo();
			sessionInfo.account = po.getAccount();
			sessionInfo.accountId = po.getId();
			sessionInfo.lastUpdateTime = LocalDateTime.now();
			sessionMap.put(sessionId, sessionInfo);
			LOGGER.info("新建会话:[{}]", sessionId);

			AuthDTO dto = new AuthDTO(SSErrorCode.SUCCESS);
			dto.setSessionId(sessionId);
			return dto;
		}
		catch (Exception e)
		{
			LOGGER.info("用户:[{}] 登录失败", account, e);
			return new AuthDTO(SSErrorCode.FAIL);
		}
	}

	@Override
	public ErrorCodeOnlyDTO logout()
	{
		try
		{
			String currentSessionId = currentSession.get();
			if (Objects.isNull(currentSessionId))
			{
				return new ErrorCodeOnlyDTO(SSErrorCode.NO_SUCH_SESSION);
			}

			synchronized (currentSessionId)
			{
				sessionMap.remove(currentSessionId);
				return new ErrorCodeOnlyDTO(SSErrorCode.SUCCESS);
			}
		}
		catch (Exception e)
		{
			LOGGER.info("登出失败", e);
			return new ErrorCodeOnlyDTO(SSErrorCode.FAIL);
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception
	{
		(new Thread(() -> {
			LOGGER.info("会话检测线程已启动");
			boolean neadStop = false;
			while (!neadStop)
			{
				LOGGER.info("开始一轮会话检测");
				LocalDateTime now = LocalDateTime.now();
				sessionMap.keySet().forEach((key) -> {
					synchronized (key.intern())
					{
						SessionInfo sessionInfo = sessionMap.get(key);
						if (Objects.isNull(sessionInfo))
						{
							LOGGER.info("会话:[{}] 已登出", key);
							return;
						}

						if (now.minusSeconds(SESSION_EXPIRE_DURATION).isAfter(sessionInfo.lastUpdateTime))
						{
							LOGGER.info("会话:[{}] 账户:[{}] 上次更新时间:[{}] 超时", key, sessionInfo.account,
									sessionInfo.lastUpdateTime);
							sessionMap.remove(key);
							LOGGER.info("会话:[{}] 已移除", key);
						}
					}
				});
				LOGGER.info("結束一轮会话检测");
				try
				{
					Thread.sleep(SESSION_CHECKER_DURATION);
				}
				catch (InterruptedException e)
				{
					LOGGER.info("收到中断信号，线程退出", e);
					return;
				}
			}
		}, "session-check-worker")).start();

	}

	static class SessionInfo
	{
		LocalDateTime lastUpdateTime;
		Long accountId;
		String account;
	}
}
